linux程式設計--open、write、lseek


Posted by nathan2009729 on 2022-07-02

程式1:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main()  //  0   1    2
{ 
  ssize_t WRLen;
  int FD = open ( "C2.c" , O_RDWR |O_APPEND );

  printf( "FD=%d\r\n", FD );   // 3    ---> write to 1

  WRLen = write ( 1 , (void *)"Hi CYH\r\n", 8) ;

  WRLen = write ( FD , (void *)"Hi CYH\r\n", 8) ;


  close(1);
  printf( "Hello CYH\r\n"); 
  WRLen = write ( 1 , (void *)"Hi_CYH\r\n", 8) ;

  close( FD );

  return 0;
}

FD:文件描述符,每多開一個文件就加1
file descriptor (fd) 基本上是一層介面,可以讓我們去操作 file 和其他 input/output interface (例如 pipe & socket)。每个进程都会预留3个默认的fd: STDIN_FILENO(stdin)、STDOUT_FILENO(stdout)、STDERR_FILENO(stderr);它们的值分别是0、1,2。
程式1裡新開文件從3開始,以上程式演示了開啟跟寫入。

開啟

(引用https://blog.jaycetyle.com/2018/12/linux-fd-open-close/)
存取檔案的操作都會需要 fd,而 fd 的取得是透過 open() 系統呼叫。open() 系統呼叫有以下兩種形式,其回傳的 int 變數就是 fd:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *name, int flags);
int open(const char *name, int flags, mode_t mode);

open() 的 flags 可以是一個或多個值 OR 的結果,用以表示開啟要求的行為,且必須包含 O_RDONLY、O_WRONLY 或 O_RDWR 三者其中之一,但如果開檔的行程不具備對應的操作權限,當然也就不能以該模式開啟檔案。
必要旗標

  • O_RDONLY: 以唯讀模式開啟
  • O_WRONLY: 以唯寫模式開啟
  • O_RDWR: 以讀寫模式開啟
    行為操作旗標
  • O_APPEND: 以附加模式開啟
    以附加模式開啟的檔案,寫入操作都會從檔案末端開始,就算第二個行程也對該檔案進行寫入因而改變了檔案末端位置
    例如多個行程要對相同 log 檔進行寫入的應用時,這個模式就會相當好用,因為它可以避免多個寫入者互相競爭的情況
  • O_CLOEXEC: 在執行 execl 後自動關閉該 fd
    fork() 後執行 execl() 是常見的操作,不過 fork() 的子行程會複製父行程的 fd
    execl() 時通常都不想保留 fd,設立此 flags 的 fd 可以在執行 execl 時自動關閉
  • O_NOATIME: 進行讀取操作時不更新檔案的存取時間
    對於檢索、備份類會經常大量讀取系統上所有檔案的程式有很大的幫助,可以減少讀取後要更新 inode 造成的寫入量
  • O_TRUNC: 若開啟的檔案存在且是一般檔案,開啟後就將其截短成長度為 0
    以 O_RDONLY | O_TRUNC 開啟檔案是未定義行為
    同步、非同步旗標
  • O_SYNC: 以 SYNC 模式開啟檔案 (之後會再補充同步 IO)
  • O_ASYNC: 如果指定的檔案變成可讀或可寫的狀態,就產生一個 Signal (預設為 SIGIO)
    用於 Socket、FIFO、Terminal 等需要非同步操作的情境
    不能用於一般檔案
    比較常見的應該是使用 aio 相關函式庫而不是使用 O_ASYNC 模式
  • O_NONBLOCK: 以 non-blocking IO 模式開啟
    對 FIFO 做 read() 時,如果沒有資料可以讀取時立即返回(不阻擋)
    因為沒資料而返回時會將 errno 設為 EAGAIN
  • O_DIRECT: 以 Direct IO 模式開啟檔案 (之後會再補充 Direct IO)
    建檔相關旗標
  • O_CREAT: 如果指定的檔案不存在,就建立一個
  • O_EXCL: 有指定 O_CREAT 時才有效。如果開檔時檔案已經存在,就回傳失敗,可以避免兩個行程想同時建檔的競爭情況
    可能比較少用的旗標
  • O_DIRECTORY: 如果開的檔案不是一個目錄就回傳失敗
  • O_LARGEFILE: 採用 64 位元 Offset 開啟檔案,可開啟 2GB 以上大檔,在 64 位元系統是預設值
  • O_NOCTTY: 和終端機相關
  • O_NOFOLLOW: 如果開啟的檔案是 symbolic link,就開啟失敗,但開啟的檔案所屬的目錄是 symbolic link 的話仍然會成功

新檔案的使用權限
  新檔案的使用權限是由 open 的第三個引數 umode_t mode 做指定。如果沒帶 O_CREAT 旗標,該引數會被忽略,但如果有 O_CREAT 旗標但卻沒有指定 mode 的話,行為會是不明確的。mode 引數就是一般的 Unix 權限位元設定,可以使用 POSIX 預定義的幾個常數用 OR 運算結合指定:

  • S_IRUSR: User 擁有 r 權限
  • S_IWUSR: User 擁有 w 權限
  • S_IXUSR: User 擁有 x 權限
  • S_IRWXU: S_IRUSR | S_IWUSR | S_IXUSR
  • S_IRGRP: Group 擁有 r 權限
  • S_IWGRP: Group 擁有 w 權限
  • S_IXGRP: Group 擁有 x 權限
  • S_IRWXG: S_IRGRP | S_IWGRP | S_IXGRP
  • S_IROTH: Other 擁有 r 權限
  • S_IWOTH: Other 擁有 w 權限
  • S_IXOTH: Other 擁有 x 權限
  • S_IRWXO: S_IROTH | S_IWOTH | S_IXOTH
      實際上儲存的權限還會受到 umask 影響,是一個行程屬性,他會遮蔽 mode 中對應的權限位元,例如 022 的 mask 會把 0666 的權限變成 0644。相關的處理可以在核心 namei.c 的 lookup_open() 中找到。只要講到行程屬性,就有很大的機會會在核心 linux/sched.h 的 task_struct ,而他實際上也是在那裡,位於 task_struct 中的 fs_struct *fs 結構內,umask 系統呼叫也是直接修改該值。

所以以上程式中

int FD = open ( "C2.c" , O_RDWR |O_APPEND );

表示打開一個可供讀寫的C2.c,且寫入到文件底部。

寫入

(引用自https://blog.jaycetyle.com/2019/01/linux-read-write/)
write函式寫入參數如下:

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

 write() 系統呼叫會從 buf 中把 count 位元組的資料寫入 fd 所參照檔案的當前位置,執行成功時會回傳寫進入檔案的位元組數,檔案位置也會前進寫入的位元組數,執行失敗時會回傳 -1 並設定 errno。write() 不像 read() 會遇到 EOF,比較不容易發生回傳值不等於 count,但如果 write 的對象是 socket 的話,就仍然要處理只有部分寫入的情況 (回傳小於 count 但大於等於 0)。

所以上述程式中

WRLen = write ( 1 , (void *)"Hi CYH\r\n", 8) ;  
WRLen = write ( FD , (void *)"Hi CYH\r\n", 8) ;

是分別寫到標準輸出跟C2.c了。

關閉

close() 系統呼叫
  應用程式使用完 fd 後,可以用 close() 系統呼叫將 fd 從檔案表中移出,核心主要的實作函式為 fs/file.c 內的 __close_fd()。另外,在 fd 回收完成後,核心也會呼叫檔案的 flush()。

程式2

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char* argv[] )  //  0   1    2
{ // cyhCP  SRCFile  TargFILE[enter]
  if( argc == 3 )
  {  char readBUF[ 64 ];  ssize_t readLen;
      int SRCFD =  open (  argv[1], O_RDWR );
      int targFD = open (  argv[2], O_RDWR  | O_CREAT | O_TRUNC ); 

      while( (readLen = read ( SRCFD , (void*)readBUF, 64 ) ) > 0 )
         write (targFD, (void*)readBUF , readLen );

      close ( SRCFD ) ;
      close ( targFD );
  }
  else
     printf( "USAGE: cyhCP  SRCFile  TargFILE[enter]\r\n" );

  return 0;
}

以上程式可以拿來複製文件,值得注意的是
argv[1]->執行的第一個參數
argv[2]->執行的第二個參數

程式3

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char* argv[] )  //  0   1    2
{ // cyhCP  SRCFile  TargFILE[enter]
  if( argc == 3 )
  {  char readBUF;   ssize_t readLen;
      int SRCFD =  open (  argv[1], O_RDWR );
      int targFD = open (  argv[2], O_RDWR  | O_CREAT | O_TRUNC ); 

      while( (readLen = read ( SRCFD , (void*)&readBUF, 1 ) ) > 0 )
      {
         write (targFD, (void*)&readBUF , readLen );
          lseek ( SRCFD , 1, SEEK_CUR );
      }
      close ( SRCFD ) ;
      close ( targFD );
  }
  else
     printf( "USAGE: cyhCP  SRCFile  TargFILE[enter]\r\n" );

  return 0;
}

lseek

(引用自https://blog.jaycetyle.com/2019/01/linux-read-write/)

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t pos, int origin);

lseek() 系統呼叫用來設定檔案的當前位置,本身不會產生任何 IO,成功時會回傳檔案的當前位置,失敗時回傳 -1 並設定 errn。lseek() 的 pos 可以是正、負或是零,實際的行為取決於 origin,可以是以下幾種的其中一個:

  • SEEK_CUR: 位置設定為當前位置加上 pos
    pos 是0時可以用來查詢目前檔案位置
  • SEEK_END: 位置設定為當前的長度再加上 pos,也就是從結尾開始算的意思
    pos 是0時可以把當前位置設為檔案結尾
  • SEEK_SET: 位置設定為 pos,也就是從開頭開始算的意思
    pos 是0時可以把當前位置設為檔案開頭
      lseek() 也可以把檔案當前位置設定到檔案結尾以後。此時如果進行讀取的話,會回傳 EOF,若進行寫入的話,中間被跳過的部分會以 hole 的形式填補成 0 而變成 sparse file。

  另外 Linux 還有提供了結合 read()、write() 及 lssek() 的變體,他們會從 pos 的位置做讀取或寫入,且完成後不會更動檔案的目前位置,且他也可以避免多執行緒情境下先做 lseek() 再做 read()、write() 可能的 race 情況。

#define _XOPEN_SOURCE 500
#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count);
ssize_t pwrite(int fd, const void *buf, size_t count);

#read #write #lseek







Related Posts

[心得] 滑鼠們

[心得] 滑鼠們

[Web] Nginx+IIS+asp.net mvc 實現負載平衡(load balancing)

[Web] Nginx+IIS+asp.net mvc 實現負載平衡(load balancing)

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?


Comments